{ "cells": [ { "cell_type": "markdown", "id": "57a92274", "metadata": {}, "source": [ "# Solar and Wind Curtailment\n", "\n", "Renewables like wind and solar regularly produce energy in excess of demand. In order to keep supply and demand balanced on the grid, the result is \"curtailment\", or purposefully reducing output.\n", "\n", "In this notebook, we'll walk through accessing the curtailment data for CAISO" ] }, { "cell_type": "code", "execution_count": 1, "id": "3599dfce", "metadata": {}, "outputs": [], "source": [ "import gridstatus\n", "import pandas as pd\n", "import plotly.express as px\n", "import numpy as np\n", "from plotly.subplots import make_subplots\n", "import plotly.graph_objects as go" ] }, { "cell_type": "code", "execution_count": 2, "id": "4304ea16", "metadata": {}, "outputs": [], "source": [ "iso = gridstatus.CAISO()" ] }, { "cell_type": "markdown", "id": "a23c3119", "metadata": {}, "source": [ "## Get Curtailment Data\n", "\n", "First, we will query for curtailment data. CAISO publishes curtailment data starting on June 30, 2016. We use the `save_to` parameter to save the data locally, so it is easier to reload later." ] }, { "cell_type": "code", "execution_count": 3, "id": "fc53269c", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 1184/1184 [52:58<00:00, 2.68s/it] \n" ] } ], "source": [ "df = iso.get_curtailment(\n", " start=\"Jan 1, 2020\", end=\"Mar 30, 2023\", save_to=\"curtailment/\"\n", ")" ] }, { "cell_type": "markdown", "id": "bd4b87c4", "metadata": {}, "source": [ "we can easily reload the data in the curtailment folder like this. By default it loads with UTC timezone unless we specify otherwise." ] }, { "cell_type": "code", "execution_count": 5, "id": "3a92c4c4", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 1184/1184 [00:00<00:00, 1270.60it/s]\n" ] } ], "source": [ "df = gridstatus.load_folder(\"curtailment\", time_zone=gridstatus.CAISO.default_timezone)\n", "\n", "# use interval start only\n", "df = df.drop(columns=[\"Time\", \"Interval End\"])" ] }, { "cell_type": "markdown", "id": "9b816252", "metadata": {}, "source": [ "next, let's reformat the data to make it easier to work with\n" ] }, { "cell_type": "code", "execution_count": 20, "id": "3724561f", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Local Solar Curtailment (MWh)Local Wind Curtailment (MWh)System Solar Curtailment (MWh)System Wind Curtailment (MWh)Total Solar Curtailment (MWh)Total Wind Curtailment (MWh)Total Curtailment (MWh)
Interval Start
2020-01-01 08:00:00-08:000.00.027.06.027.06.033.0
2020-01-01 09:00:00-08:0078.07.0138.013.0216.020.0236.0
2020-01-01 10:00:00-08:0012.00.0917.043.0929.043.0972.0
2020-01-01 11:00:00-08:0019.00.01229.044.01248.044.01292.0
2020-01-01 12:00:00-08:00216.025.01194.0131.01410.0156.01566.0
........................
2023-03-29 13:00:00-07:00204.04.00.00.0204.04.0208.0
2023-03-29 14:00:00-07:00133.00.00.00.0133.00.0133.0
2023-03-29 15:00:00-07:0085.00.00.07.085.07.092.0
2023-03-29 16:00:00-07:008.00.00.00.08.00.08.0
2023-03-29 17:00:00-07:001.00.00.00.01.00.01.0
\n", "

28401 rows × 7 columns

\n", "
" ], "text/plain": [ " Local Solar Curtailment (MWh) \\\n", "Interval Start \n", "2020-01-01 08:00:00-08:00 0.0 \n", "2020-01-01 09:00:00-08:00 78.0 \n", "2020-01-01 10:00:00-08:00 12.0 \n", "2020-01-01 11:00:00-08:00 19.0 \n", "2020-01-01 12:00:00-08:00 216.0 \n", "... ... \n", "2023-03-29 13:00:00-07:00 204.0 \n", "2023-03-29 14:00:00-07:00 133.0 \n", "2023-03-29 15:00:00-07:00 85.0 \n", "2023-03-29 16:00:00-07:00 8.0 \n", "2023-03-29 17:00:00-07:00 1.0 \n", "\n", " Local Wind Curtailment (MWh) \\\n", "Interval Start \n", "2020-01-01 08:00:00-08:00 0.0 \n", "2020-01-01 09:00:00-08:00 7.0 \n", "2020-01-01 10:00:00-08:00 0.0 \n", "2020-01-01 11:00:00-08:00 0.0 \n", "2020-01-01 12:00:00-08:00 25.0 \n", "... ... \n", "2023-03-29 13:00:00-07:00 4.0 \n", "2023-03-29 14:00:00-07:00 0.0 \n", "2023-03-29 15:00:00-07:00 0.0 \n", "2023-03-29 16:00:00-07:00 0.0 \n", "2023-03-29 17:00:00-07:00 0.0 \n", "\n", " System Solar Curtailment (MWh) \\\n", "Interval Start \n", "2020-01-01 08:00:00-08:00 27.0 \n", "2020-01-01 09:00:00-08:00 138.0 \n", "2020-01-01 10:00:00-08:00 917.0 \n", "2020-01-01 11:00:00-08:00 1229.0 \n", "2020-01-01 12:00:00-08:00 1194.0 \n", "... ... \n", "2023-03-29 13:00:00-07:00 0.0 \n", "2023-03-29 14:00:00-07:00 0.0 \n", "2023-03-29 15:00:00-07:00 0.0 \n", "2023-03-29 16:00:00-07:00 0.0 \n", "2023-03-29 17:00:00-07:00 0.0 \n", "\n", " System Wind Curtailment (MWh) \\\n", "Interval Start \n", "2020-01-01 08:00:00-08:00 6.0 \n", "2020-01-01 09:00:00-08:00 13.0 \n", "2020-01-01 10:00:00-08:00 43.0 \n", "2020-01-01 11:00:00-08:00 44.0 \n", "2020-01-01 12:00:00-08:00 131.0 \n", "... ... \n", "2023-03-29 13:00:00-07:00 0.0 \n", "2023-03-29 14:00:00-07:00 0.0 \n", "2023-03-29 15:00:00-07:00 7.0 \n", "2023-03-29 16:00:00-07:00 0.0 \n", "2023-03-29 17:00:00-07:00 0.0 \n", "\n", " Total Solar Curtailment (MWh) \\\n", "Interval Start \n", "2020-01-01 08:00:00-08:00 27.0 \n", "2020-01-01 09:00:00-08:00 216.0 \n", "2020-01-01 10:00:00-08:00 929.0 \n", "2020-01-01 11:00:00-08:00 1248.0 \n", "2020-01-01 12:00:00-08:00 1410.0 \n", "... ... \n", "2023-03-29 13:00:00-07:00 204.0 \n", "2023-03-29 14:00:00-07:00 133.0 \n", "2023-03-29 15:00:00-07:00 85.0 \n", "2023-03-29 16:00:00-07:00 8.0 \n", "2023-03-29 17:00:00-07:00 1.0 \n", "\n", " Total Wind Curtailment (MWh) \\\n", "Interval Start \n", "2020-01-01 08:00:00-08:00 6.0 \n", "2020-01-01 09:00:00-08:00 20.0 \n", "2020-01-01 10:00:00-08:00 43.0 \n", "2020-01-01 11:00:00-08:00 44.0 \n", "2020-01-01 12:00:00-08:00 156.0 \n", "... ... \n", "2023-03-29 13:00:00-07:00 4.0 \n", "2023-03-29 14:00:00-07:00 0.0 \n", "2023-03-29 15:00:00-07:00 7.0 \n", "2023-03-29 16:00:00-07:00 0.0 \n", "2023-03-29 17:00:00-07:00 0.0 \n", "\n", " Total Curtailment (MWh) \n", "Interval Start \n", "2020-01-01 08:00:00-08:00 33.0 \n", "2020-01-01 09:00:00-08:00 236.0 \n", "2020-01-01 10:00:00-08:00 972.0 \n", "2020-01-01 11:00:00-08:00 1292.0 \n", "2020-01-01 12:00:00-08:00 1566.0 \n", "... ... \n", "2023-03-29 13:00:00-07:00 208.0 \n", "2023-03-29 14:00:00-07:00 133.0 \n", "2023-03-29 15:00:00-07:00 92.0 \n", "2023-03-29 16:00:00-07:00 8.0 \n", "2023-03-29 17:00:00-07:00 1.0 \n", "\n", "[28401 rows x 7 columns]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df[\"Type\"] = (\n", " df[\"Curtailment Reason\"].str.lower().str.capitalize()\n", " + \" \"\n", " + df[\"Fuel Type\"]\n", " + \" Curtailment (MWh)\"\n", ")\n", "curtailment = df.pivot_table(\n", " values=\"Curtailment (MWh)\",\n", " index=\"Interval Start\",\n", " columns=\"Type\",\n", " # sum to account for 'Economic' and 'SelfSchCut' reasons\n", " aggfunc=\"sum\",\n", ").fillna(0)\n", "\n", "curtailment[\"Total Solar Curtailment (MWh)\"] = (\n", " curtailment[\"Local Solar Curtailment (MWh)\"]\n", " + curtailment[\"System Solar Curtailment (MWh)\"]\n", ")\n", "curtailment[\"Total Wind Curtailment (MWh)\"] = (\n", " curtailment[\"Local Wind Curtailment (MWh)\"]\n", " + curtailment[\"System Wind Curtailment (MWh)\"]\n", ")\n", "curtailment[\"Total Curtailment (MWh)\"] = (\n", " curtailment[\"Total Solar Curtailment (MWh)\"]\n", " + curtailment[\"Total Wind Curtailment (MWh)\"]\n", ")\n", "curtailment.columns.name = None\n", "curtailment = curtailment.resample(\"1H\").sum()\n", "curtailment" ] }, { "cell_type": "markdown", "id": "e99733e3", "metadata": {}, "source": [ "## Visualizing Curtailment" ] }, { "attachments": {}, "cell_type": "markdown", "id": "f456daea", "metadata": {}, "source": [ "### Cumulative Curtailment by Year" ] }, { "cell_type": "code", "execution_count": 43, "id": "86e2c9ec", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": "Jan 01Jan 07Jan 13Jan 19Jan 25Jan 31Feb 06Feb 12Feb 18Feb 24Mar 01Mar 07Mar 13Mar 19Mar 25Mar 31Apr 06Apr 12Apr 18Apr 24Apr 30May 06May 12May 18May 24May 30Jun 05Jun 11Jun 17Jun 23Jun 29Jul 05Jul 11Jul 17Jul 23Jul 29Aug 04Aug 10Aug 16Aug 22Aug 28Sep 03Sep 09Sep 15Sep 21Sep 27Oct 03Oct 09Oct 15Oct 21Oct 27Nov 02Nov 08Nov 14Nov 20Nov 26Dec 02Dec 08Dec 14Dec 20Dec 2600.5M1M1.5M2M2.5MYear2020202120222023Cumalative Curtailment (MWh)Day of YearTotal Curtailment (MWh)" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "daily_curtailment = curtailment.resample(\"1D\").sum()\n", "cumulative_curtailment = (\n", " daily_curtailment.groupby(daily_curtailment.index.year)[\"Total Curtailment (MWh)\"]\n", " .cumsum()\n", " .reset_index()\n", ")\n", "cumulative_curtailment[\"Year\"] = cumulative_curtailment[\"Interval Start\"].dt.year\n", "cumulative_curtailment[\"Day of Year\"] = cumulative_curtailment[\n", " \"Interval Start\"\n", "].dt.strftime(\"%b %d\")\n", "\n", "# plot all years together\n", "fig = px.line(\n", " cumulative_curtailment,\n", " y=\"Total Curtailment (MWh)\",\n", " x=\"Day of Year\",\n", " title=\"Cumalative Curtailment (MWh)\",\n", " color=\"Year\",\n", ")\n", "\n", "fig.show(\"svg\", width=1200, height=600)" ] }, { "cell_type": "markdown", "id": "50a5085d", "metadata": {}, "source": [ "### Monthly Curtailment" ] }, { "cell_type": "code", "execution_count": 41, "id": "ed6b9ee4", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": "Jul 2020Jan 2021Jul 2021Jan 2022Jul 2022Jan 20230100k200k300k400k500k600kTotal Solar Curtailment (MWh)Total Wind Curtailment (MWh)Monthly Solar and Wind Curtailment in CAISO (MWh)Interval StartCurtailment (MWh)" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "monthly = curtailment.resample(\"M\").sum()\n", "monthly[\"Month\"] = monthly.index.month\n", "monthly[\"Year\"] = monthly.index.year\n", "\n", "fig = px.bar(\n", " monthly,\n", " x=monthly.index,\n", " y=[\"Total Solar Curtailment (MWh)\", \"Total Wind Curtailment (MWh)\"],\n", " title=\"Monthly Solar and Wind Curtailment in CAISO (MWh)\",\n", ")\n", "\n", "# legend upper left corner\n", "fig.update_layout(\n", " legend=dict(\n", " orientation=\"h\", yanchor=\"bottom\", y=1.02, xanchor=\"left\", x=0, title_text=None\n", " )\n", ")\n", "fig.update_yaxes(title_text=\"Curtailment (MWh)\")\n", "fig.show(\"svg\", width=1200, height=600)" ] }, { "cell_type": "markdown", "id": "d00a5e66", "metadata": {}, "source": [ "### Average Hourly Curtailment" ] }, { "cell_type": "code", "execution_count": 42, "id": "a4a70869", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": "051015200100200300400500600700variableTotal Solar Curtailment (MWh)Total Wind Curtailment (MWh)Average Hourly Solar and Wind Curtailment in CAISO (MWh)Interval StartCurtailment (MWh)" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "avg_hourly = curtailment.groupby(curtailment.index.hour).mean()\n", "fig = px.line(\n", " avg_hourly,\n", " x=avg_hourly.index,\n", " y=[\"Total Solar Curtailment (MWh)\", \"Total Wind Curtailment (MWh)\"],\n", " title=\"Average Hourly Solar and Wind Curtailment in CAISO (MWh)\",\n", ")\n", "fig.update_yaxes(title_text=\"Curtailment (MWh)\")\n", "fig.show(\"svg\", width=1200, height=600)" ] }, { "cell_type": "code", "execution_count": 9, "id": "9474c2be", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": "2020202120222022 YTD00.5M1M1.5M2M2.5MTotal Solar Curtailment (MWh)Total Wind Curtailment (MWh)Total Solar and Wind Curtailment in CAISO (MWh)YearCurtailment (MWh)" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "curtailment[\"Year\"] = curtailment.index.year\n", "yearly_sum = curtailment.groupby(\"Year\").sum()\n", "index = yearly_sum.index.astype(str).tolist()\n", "index[-1] = \"2022 YTD\"\n", "yearly_sum.index = index\n", "\n", "fig = px.bar(\n", " yearly_sum,\n", " x=yearly_sum.index,\n", " y=[\"Total Solar Curtailment (MWh)\", \"Total Wind Curtailment (MWh)\"],\n", " title=\"Total Solar and Wind Curtailment in CAISO (MWh)\",\n", ")\n", "fig.update_layout(\n", " legend=dict(yanchor=\"bottom\", y=0.9, xanchor=\"left\", x=0, title_text=None)\n", ")\n", "fig.update_yaxes(title_text=\"Curtailment (MWh)\")\n", "fig.update_xaxes(title_text=\"Year\")\n", "fig.show(\"svg\", width=1200, height=600)" ] }, { "cell_type": "code", "execution_count": null, "id": "9d398848", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.2" }, "vscode": { "interpreter": { "hash": "49f14642123d0cc1afa9fa45716ed5f1e915189c28b01efe02a8b7ec3c0a3fce" } } }, "nbformat": 4, "nbformat_minor": 5 }